#!/usr/bin/python3

import requests
import json

from http.server import  HTTPServer, BaseHTTPRequestHandler
import time
import threading
import random

##  api : 2023-12-20


#vod_id=from_bilibili/aid/cid
#season_id=from_bilibili/season_id_sid/cid
FROM_BILIBILI="from_bilibili"
SEASON_PREFIX="season_id_"

none_url="http://v3.tv.server.lan/files/videos/noTv.mp4"

# qn =
## 1080p=112
## 720p 64
## 480p 32
## 360p 16
qn_720p=64
qn_480p=32
qn_360p=16


def copy_kv(src,dst):
    for k in src.keys():
        dst[k]=src[k]
    return dst

# header0={ "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" }
header0={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"}
header={'Referer': 'https://www.bilibili.com'}

header_result={
    #"__ffff__":"p2"
    "__ffff__":"p2;;ooo"
}

copy_kv(header0,header)
copy_kv(header,header_result)


cookies=None






def getCookie():
    page=requests.get("https://www.bilibili.com/",headers=header0)
    cook=page.cookies
    return cook

def fetch(url):
    global cookies
    if not cookies:
        cookies=getCookie()
    if not cookies:
        print("[E] error : can't get cookies ")
        return
    return requests.get(url,cookies=cookies,headers=header)


threading_1_args={}
flag_exit_thread=False

def _check_exit_and_sleep_30sec(n):
    for i in range(n):
        if flag_exit_thread:
            return True
        time.sleep(30)
    return False


def threading_1_job():
    # do nothing
    return

    while True:
        count=threading_1_args.get("count",0)
        if count<=0:
            if _check_exit_and_sleep_30sec(4):
                break
            continue

        threading_1_args["count"]=0
        for _ in range(5):
            # 10 min
            if _check_exit_and_sleep_30sec(20):
                break

            aid=threading_1_args.get("aid","")
            if len(aid)==0:
                continue

            t= time.ctime()
            if aid.startswith(SEASON_PREFIX):
                # ssxxxx
                aid=aid.removeprefix(SEASON_PREFIX)
                url="https://www.bilibili.com/bangumi/play/ss"+aid
            else:
                # avxxx
                url="https://www.bilibili.com/video/av"+aid

            try:
                x=fetch(url)
                if x.status_code==200:
                    print(t,"heart beat OK: 200 ,",url)
                else:
                    print(t,"heart beat not OK? : ",x.status_code,url)
            except Exception as e:
                    print(t,"heart beat exception : ",url)


def _get_url(jo):
    if not jo:
        return ""
    ja = jo.get('durl',None)
    if not ja:
        return ""
    if len(ja)<=0:
        return ""
    maxSize = -1
    position = 0
    # print(ja)
    for i in range(len(ja)):
        # 获取最高清晰度!!!
        tmpJo = ja[i]
        tmpMax=int(tmpJo.get('size',0))
        if maxSize < tmpMax:
            maxSize = tmpMax
            position = i

    url = ja[position]['url']
    if position==0:
        return url

    if not (url.startswith("https://") or url.startswith("http://")):
        url = ja[0]['url']

    return url

#res_none={
#    "parse":0,
#    "playUrl":"",
#    "url":"https://server.lan/noTv.mp4",
#    "header":header0,
#    "contentType":'video/x-flv'
#}
#res_none=json.dumps(res_none)

#res_none={
#    "playUrl":"https://server.lan/noTv.mp4",
#    "url":""
#}
#res_none=json.dumps(res_none)

def get_season_VideoUrl(flag,id):
    ids = id.split("_")
    #qn 116
    # url = 'https://api.bilibili.com/pgc/player/web/playurl?ep_id={0}&cid={1}&qn={2}'.format(ids[0],ids[1],qn_480p)
    url = 'https://api.bilibili.com/pgc/player/web/playurl?ep_id={0}&cid={1}&qn={2}'.format(ids[0],ids[1],0)
    # &fnver=0&fnval=4048

    #  uuu='https://api.bilibili.com/pgc/player/web/v2/playurl?qn=0&from_client=BROWSER&ep_id={}&cid={}'.format(ids[0],ids[1])

#https://api.bilibili.com/pgc/player/web/v2/playurl?support_multi_audio=true&avid=209227199&cid=4837199&qn=0&fnver=0&fnval=4048&fourk=1&gaia_source=&from_client=BROWSER&ep_id=148741&session=229d4d2dcfccfb38a0a76e97208e9bdf&drm_tech_type=2
    rsp = fetch(url)
    jRoot = json.loads(rsp.text)
    if jRoot['message'] != 'success':
        print("需要大会员权限才能观看")
        return '{}'

    result = {}
    jo = jRoot.get('result',{})
    url=""
    try:
        url=_get_url(jo)
    except Exception as e:
        print("[E] _get_url , err:",e)

    # # b站视频, ijk会每10分钟 出错一次. errRty 卡2秒 # 换exo试试
    # result["pl"]=2 # 0=系统,1=ijk,2=exo
    # result["ijk"]="软解码"
    result["parse"] = 0
    result["playUrl"] = ''
    result["url"] = url
    result["header"] = header_result
    result["contentType"] = 'video/x-flv'
    return json.dumps(result)

def getVideoUrl(aid,cid):
    if aid.startswith(SEASON_PREFIX):
        # #season_id=from_bilibili/season_id_sid/cid
        print("[i] try get season video list")
        try:
            # eid, cid
            return get_season_VideoUrl(aid,cid)
        except Exception as e:
            print("[E] get season video list error",e)
            return ""

    # play
    #qn 112
    # v_url = 'https://api.bilibili.com/x/player/playurl?avid={0}&cid=%20%20{1}&qn={2}'.format(aid,cid,qn_720p)
    v_url = 'https://api.bilibili.com/x/player/playurl?avid={0}&cid=%20%20{1}&qn={2}'.format(aid,cid,112)
    v_url = 'https://api.bilibili.com/x/player/playurl?avid={0}&cid={1}&qn={2}'.format(aid,cid,112)

    try:
        res=fetch(v_url)
        print("play: http_code=",res.status_code)
        jRoot=json.loads(res.text)
        jo=jRoot.get('data',{})
        print(res.text)
        if not jo:
            return ""
        url=_get_url(jo)
        if not url:
            url=jo["durl"][0]["url"]
        # backup_url=vvv["data"]["durl"][0]["backup_url"]
        result={}


        # # b站视频, ijk会每10分钟 出错一次. errRty 卡2秒 # 换exo试试
        # result["pl"]=2 # 0=系统,1=ijk,2=exo
        # result["ijk"]="软解码"
        result["parse"] = 0
        result["playUrl"] = ''
        result["url"] = url
        result["header"] = header_result
        result["contentType"] = 'video/x-flv'
        return json.dumps(result)
    except Exception as e:
        print(e)
    result={}
    result["playUrl"] = ''
    result["url"] = ""
    return json.dumps(result)

def get_season_VideoList(season_id):
    sid=season_id.removeprefix(SEASON_PREFIX)
    url = "http://api.bilibili.com/pgc/view/web/season?season_id={0}".format(sid)
    rsp = fetch(url)
    jRoot = json.loads(rsp.text)
    jo = jRoot['result']

    playUrl = ''
    for tmpJo in jo.get('episodes',[]):
        eid = tmpJo.get('id','')
        cid = tmpJo.get('cid','')
        part = tmpJo.get('title','').replace("#", "-")
        playUrl = playUrl + '{}${}/{}/{}_{}#'.format(part,FROM_BILIBILI,season_id,eid, cid)
    return playUrl

    # id = jo.get('season_id',season_id)
    # title = jo.get('title',"")
    # pic = jo.get('cover',"")
    # typeName = jo.get('share_sub_title',"")
    # dec = jo.get('evaluate',"")
    # areas = ""
    # remark = ""
    # try:
    #     areas = jo['areas'][0]['name']
    # except Exception as e:
    #     pass
    # try:
    #     remark = jo['new_ep']['desc']
    # except Exception as e:
    #     pass

    # vod = {
    #     "vod_id":id,
    #     "vod_name":title,
    #     "vod_pic":pic,
    #     "type_name":typeName,
    #     "vod_year":"",
    #     "vod_area":areas,
    #     "vod_remarks":remark,
    #     "vod_actor":"",
    #     "vod_director":"",
    #     "vod_content":dec
    # }

    # playUrl = ''
    # for tmpJo in jo.get('episodes',[]):
    #     eid = tmpJo.get('id','')
    #     cid = tmpJo.get('cid','')
    #     part = tmpJo.get('title','').replace("#", "-")
    #     playUrl = playUrl + '{}${}/{}/{}_{}#'.format(part,FROM_BILIBILI,season_id,eid, cid)
    # vod['vod_play_from'] = 'B站影视'
    # vod['vod_play_url'] = playUrl
    # return playUrl
    # result = {
    #     'list':[
    #         vod
    #     ]
    # }
    # return result


def getVideoList(aid):
    if aid is None:
        return ""

    if aid.startswith(SEASON_PREFIX):
        # #season_id=from_bilibili/season_id_sid/cid
        print("[i] try get season video list")
        try:
            return get_season_VideoList(aid)
        except Exception as e:
            print("[E] get season video list error",e)
            return ""
    # detail
    try:
        detail_url="https://api.bilibili.com/x/web-interface/view?aid={0}".format(str(aid))
        detail=fetch(detail_url)
        print("detail : http_code=",detail.status_code)
        
        detail_data=json.loads(detail.text)['data']
        print("detail_data , len=",len(detail_data))
        pages=detail_data["pages"]
        res=[]
        for pp in pages:
            print(pp)
        if len(pages)==1:
            page=pages[0]
            cid=page["cid"]
            title="1"
            s="%s$from_bilibili/%s/%s"%(str(title),str(aid),str(cid))
            res.append(s)

        elif len(pages)>1:
            for i,page in enumerate(pages):
                cid=page["cid"]
                title=page["part"]
                s="%02d %s$from_bilibili/%s/%s"%(i+1,str(title),str(aid),str(cid))
                res.append(s)

        return "#".join(res)
    except Exception as e:
        print(e)
        return ""


class MyCache(object):
    caches=None

    cur=0
    SIZE=10
    # 1 hour
    timeout=3600
    defaultReturnValue=""

    def __init__(self,size=10,timeout=3600,defaultReturnValue=""):
        self.caches=[]
        self.SIZE=size
        self.timeout=timeout
        self.defaultReturnValue=defaultReturnValue

    def put(self,key,data):

        if not key or not data:
            return

        ca={
            "__key":key,
            "time":time.time(),
            "data":data
        }
        if len(self.caches) > self.cur:
            self.caches[self.cur]=ca
        else:
            self.caches.append(ca)
        print("[info] cache put done",key,".")
        self.cur= (self.cur+1)%self.SIZE

    def get(self,key):
        if not key:
            return self.defaultReturnValue
        print("get from cache")
        cur=self.cur-1
        l=len(self.caches)
        for i in range(l):
            ca=self.caches[(cur-i)%l]
            # print("debug , index= ",i,ca.get("__key","NOKEY"))
            if key==ca.get("__key"):
                # 1 day = 86400
                if time.time()-ca.get("time",0) > self.timeout :
                    print("    need fresh caches , return null")
                    return self.defaultReturnValue
                print("    get !!")
                return ca.get("data",self.defaultReturnValue)
        print("    no get.")
        return self.defaultReturnValue

one_day=86400
one_hour=3600
ten_min=600
LAST_TIMESTAMP=0
plist_cache=MyCache(50,one_day,"")
real_video_cache=MyCache(40,ten_min*2,"")

# debug
# plist_cache=MyCache(20,60*2,"")
# real_video_cache=MyCache(20,60,"")
import re

patternPre="\"aid\":"
patternSuffix=","
aidPattern=re.compile("{}\d+{}".format(patternPre,patternSuffix))

BVID_TO_AID={}
BVID_TO_AID2={}

def get_aid_by_bvid(bvid):
    global BVID_TO_AID,BVID_TO_AID2
    aid=BVID_TO_AID.get(bvid)
    if aid is not None:
        return aid
    aid=BVID_TO_AID2.get(bvid)
    if aid is not None:
        return aid
    try:
        url="https://www.bilibili.com/video/{0}/".format(bvid)
        text=fetch(url).text
        match=aidPattern.search(text)
        if match:
            aid=text[match.start()+len(patternPre):match.end()-len(patternSuffix)]
            BVID_TO_AID[bvid]=aid
            if len(BVID_TO_AID.keys()) >205: # 200
                print("[i] clear BVID_TO_AID cache")
                BVID_TO_AID2=BVID_TO_AID
                BVID_TO_AID={}
            return aid
    except Exception as e:
        print("[W] get aid err",e)
        return None



def handle(url):
    # vod_id=from_bilibili/aid/cid
    url=url.strip(" /")

    x=url.split("/")
    if(not x or len(x)<=1):
        return ""
    aid=x[1]
    cid=None

    if "get_aid" in url:
        if aid.lower().startswith("bv"):
            aid=get_aid_by_bvid(aid) ##"todo"
        elif aid.lower().startswith("av"):
            aid=aid[2:]
        return aid

    if len(x)==2:
        print("[ detail ] ################")

        if aid.lower().startswith("bv"):
            aid=get_aid_by_bvid(aid) ##"todo"

        elif aid.lower().startswith("av"):
            aid=aid[2:]

        data=plist_cache.get(url)
        if data and len(data)>0:
            return data
        else:
            data=getVideoList(aid)
            if data:
                plist_cache.put(url,data)
            #if len(data)==0:
            #    data="none$from_bilibili/NONE/NONE"
            return data
    else:
        # vod_id=from_bilibili/aid/cid
        cid=x[2]
        #if cid == "NONE":
        #    return res_none

        #threading_1_args["aid"]=aid
        print("[ play ] ################")
        data=real_video_cache.get(url)
        if data and len(data)>0:
            return data
        else:
            data=getVideoUrl(aid,cid)
            if data:
                real_video_cache.put(url,data)
            return data


class MyHttp(BaseHTTPRequestHandler):

    def process_redirect(self,url):
        self.send_response(301)
        self.send_header("Location", url)
        self.end_headers()

        print("[redirect] 301:%s"%url)
        self.wfile.write(bytes(url, "utf-8"))
        return

    def do_GET(self):
        #if self.path.startswith("/https://") or self.path.startswith("/http://"):
        #    return self.process_redirect(self.path[1:])

        global cookies,LAST_TIMESTAMP

        now=time.time()
        # > 20min
        if now-LAST_TIMESTAMP >  20*60:
            cookies=getCookie()
            print("[INFO] get cookie: ",LAST_TIMESTAMP)
            LAST_TIMESTAMP=now

        #threading_1_args["count"]=3

        path=self.path.strip("/ ")
        data=handle(path)

        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()

        print("path=",path,"res_len=",len(data))
        self.wfile.write(bytes(data, "utf-8"))

    def do_POST(self):
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()

        date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
        data_str = f'{{"time":"{date}"}}'
        self.wfile.write(bytes(data_str, "utf-8"))


HOST = '0.0.0.0' # your ip v4 address
PORT = 9967
server = HTTPServer((HOST, PORT), MyHttp)

# print("start threading 1")
# th=threading.Thread(target=threading_1_job)
# th.start()

try:
    print("server is running...")
    server.serve_forever()
    server.server_close()
    print("server is closed!")
except KeyboardInterrupt:
    print("CTR+C  to exit")
    print("CTR+C  to exit")
    flag_exit_thread=True
    